// Copyright (c) 2014-present, b3log.org
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package session

import (
	"encoding/json"
	"math/rand"
	"net/http"
	"os"
	"path/filepath"
	"strconv"
	"strings"

	"chainmaker.org/chainmaker/smarteditor/global"
	"chainmaker.org/chainmaker/smarteditor/oauth/chainmaker"

	"chainmaker.org/chainmaker/smarteditor/conf"
	"chainmaker.org/chainmaker/smarteditor/container"
	"chainmaker.org/chainmaker/smarteditor/util"

	"github.com/88250/gulu"
)

func LoginCallbackHandler(userId, userName, avatar, site string, w http.ResponseWriter, r *http.Request) string {
	global.UserTokenCache.Delete(userId) //总是刷一下缓存
	user := conf.GetUser(userId)
	if nil == user {
		log.Infof("[LoginCallbackHandler] user not found. userId:%s", userId)
		msg := addUser(userId, userName, avatar)
		if userCreated != msg {
			result := gulu.Ret.NewResult()
			result.Code = -1
			result.Msg = msg
			gulu.Ret.RetResult(w, r, result)
			return ""
		}
		user = conf.GetUser(userId) //重新刷新一下user信息
	}
	log.Infof("[LoginCallbackHandler] user %v", user)
	// 扫描一下是否已经将合约目录都扫描尽了数据库
	hasScanned, hasScannedErr := conf.CheckUserHasScanned(userId)
	// log.Debugf("LoginCallbackHandler uid %s, hasScanned %+v, hasScannedErr %+v",
	// 	userId, hasScanned, hasScannedErr)
	log.Debugf("LoginCallbackHandler 1---")
	if hasScannedErr != nil {
		log.Errorf("CheckUserHasScanned userId %s , failed %s", userId, hasScannedErr.Error())
		result := gulu.Ret.NewResult()
		result.Code = -1
		result.Msg = hasScannedErr.Error()
		gulu.Ret.RetResult(w, r, result)
		return ""
	}
	log.Debugf("LoginCallbackHandler 2---")
	if hasScannedErr == nil && !hasScanned {
		// scan all contract
		scanned, _ := scanUserAllContracts(user)
		for _, scan := range scanned {
			createErr := conf.CreateUserContractHidden(&conf.UserContractHidden{UserId: userId, Contract: scan})
			if createErr != nil {
				log.Errorf("LoginCallbackHandler CreateUserContractHidden uid %s, contract %s , failed %s",
					userId, scan, createErr.Error())
			}
		}
		conf.SaveUserHasScanned(userId)
	}
	//
	// 在这里check一下是否已经把历史信息都扫描进来了
	log.Debugf("LoginCallbackHandler 3---")
	hasDeployed, hasErr := conf.CheckUserHasDeployed(userId)
	if hasErr == nil && !hasDeployed {
		// scan deploy info
		deployFile := filepath.Join(conf.GetDeployDir(conf.GetUserWorkspace(userId)), conf.GetDeployInfoName())
		deployList, deployErr := conf.ReadDeployInfo(deployFile)
		if deployErr != nil {
			log.Errorf("LoginCallbackHandler ReadDeployInfo %s failed %s", deployFile, deployErr.Error())
		}
		for i := 0; i < len(deployList); i++ {
			tmpDeploy := deployList[i]
			conf.SaveDeployInfo(tmpDeploy.TransformToDeployInfoModel(userId))
		}
		// save deploy info
		// save hasdeployed
		conf.SaveUserHasDeployed(userId)
	}
	// create a HTTP session
	httpSession, _ := HTTPSession.Get(r, CookieName)
	log.Debug("httpSession value---:", httpSession)
	httpSession.Values["uid"] = userId
	sessionId := strconv.Itoa(rand.Int())
	httpSession.Values["id"] = sessionId
	httpSession.Options.MaxAge = conf.Wide.HTTPSessionMaxAge
	httpSession.Save(r, w)
	log.Debugf("LoginCallbackHandler 4---")
	container.SC.SessionManager.AddSid(sessionId)
	err := container.SC.ResourceManager.InitOrUpdateUserResourcesInfo(userId)
	if err != nil {
		log.Errorf("[LoginCallbackHandler] init or update user resource info fail. uid:%s, err：%v", userId, err)
		result := gulu.Ret.NewResult()
		result.Code = -1
		result.Msg = err.Error()
		return ""
	}
	log.Debugf("LoginCallbackHandler 5---")
	//remove old session
	sessions := WideSessions.GetByUserId(userId)
	if len(sessions) > 0 {
		log.Debugf("[LoginRedirectHandler] remove old session, start. session count:%d", len(sessions))
		sids := make([]string, len(sessions), len(sessions))
		for index, session := range sessions {
			session.HTTPSession.Options.MaxAge = -1
			sids[index] = session.ID
			container.SC.SessionManager.RemoveSid(session.ID)
			notificationWS, err := GetNotificationWS(session.ID)
			if err != nil {
				log.Warn("[LoginRedirectHandler] remove old session, notification ws not found, processing. index:%d, session:%v", index, session)
				continue
			}
			SendLoginOutMessageToClient(session.ID, notificationWS)
			WideSessions.Remove(session.ID)
			log.Debugf("[LoginRedirectHandler] remove old session, processing. index:%d, session:%v", index, session)
		}
		log.Debugf("[LoginRedirectHandler] remove old session, end")
	}
	log.Debugf("LoginCallbackHandler 6---")
	http.Redirect(w, r, site, http.StatusSeeOther)
	return sessionId
}

func scanUserAllContracts(u *conf.User) ([]string, error) {
	var retArray []string
	srcPath := conf.GetSrcDir(u.Workspace)
	file, err := os.OpenFile(srcPath, os.O_RDONLY, 0)
	if err != nil {
		log.Errorf("scanUserAllContracts OpenFile uid %s, failed %s",
			u.ID, err.Error())
		return retArray, err
	}
	defer file.Close()
	fileNames, err := file.Readdirnames(-1)
	if err != nil {
		log.Errorf("scanUserAllContracts Readdirnames uid %s, failed %s",
			u.ID, err.Error())
		return retArray, err
	}
	for _, fileName := range fileNames {
		path := filepath.Join(srcPath, fileName)
		if !gulu.File.IsDir(path) {
			continue
		}

		retArray = append(retArray, fileName)
	}
	return retArray, nil
}

func SendLoginOutMessageToClient(sid string, channel *util.WSChannel) {
	log.Debugf("[SendLoginOutMessageToClient] send logout message to client. sid:%s", sid)
	defer util.PanicLog()
	message := make(map[string]string)
	message["cmd"] = "logout"
	err := channel.WriteJSON(message)
	if err != nil {
		log.Warnf("[SendLoginOutMessageToClient] fail. err:%v", err)
	}
}

// // LoginHandler handles request of show login page.
// func LoginHandler2(w http.ResponseWriter, r *http.Request) {
// 	model := map[string]interface{}{
// 		"conf":   conf.Wide,
// 		"i18n":   i18n.GetAll(conf.Wide.Locale),
// 		"locale": conf.Wide.Locale, "ver": conf.WideVersion,
// 		"year": time.Now().Year(),
// 	}

// 	t, err := template.ParseFiles("views/login.html")
// 	if nil != err {
// 		log.Errorf("[LoginHandler] parse templete fail. err:%v", err)
// 		http.Error(w, err.Error(), http.StatusInternalServerError)
// 		return
// 	}

// 	err = t.Execute(w, model)
// 	if err != nil {
// 		log.Errorf("[LoginHandler] render templete fail. err:%v", err)
// 		http.Error(w, err.Error(), http.StatusInternalServerError)
// 		return
// 	}
// }

// // LoginHandler handles request of show login page.
// func LoginHandler(w http.ResponseWriter, r *http.Request) {
// 	model := map[string]interface{}{
// 		"conf":   conf.Wide,
// 		"i18n":   i18n.GetAll(conf.Wide.Locale),
// 		"locale": conf.Wide.Locale,
// 		"ver":    conf.WideVersion,
// 		"year":   time.Now().Year(),
// 	}

// 	result := gulu.Ret.NewResult()
// 	result.Data = model
// 	gulu.Ret.RetGzResult(w, r, result)
// }

// LogoutHandler handles request of user logout (exit).
func LogoutHandler(w http.ResponseWriter, r *http.Request) {
	result := gulu.Ret.NewResult()
	defer gulu.Ret.RetResult(w, r, result)

	httpSession, _ := HTTPSession.Get(r, CookieName)
	container.SC.Logger.Debugf("[LogoutHandler], user log out. httpSession :%v", httpSession)

	sessionId := httpSession.Values["id"]
	httpSession.Options.MaxAge = -1
	container.SC.Logger.Debugf("[LogoutHandler], user log out. sessionId :%v", sessionId)
	if oidci, ok := global.UserTokenCache.LoadAndDelete(sessionId); ok {
		oidc := oidci.(*chainmaker.OpenIdInfo)
		logoutUrl := conf.Wide.ChainMakerOAuthServer + conf.Wide.ChainMakerBaseUrl + conf.Wide.ChainMakerLogoutUrl
		chainmaker.Logout(logoutUrl, oidc.AccessToken, oidc.RefreshToken)
	}
	if sessionId != nil {
		container.SC.SessionManager.RemoveSid(sessionId.(string))
	}
	httpSession.Save(r, w)
}

// addUser add a user with the specified user id, username and avatar.
//
//  1. create the user's workspace
//  2. generate 'Hello, 世界' demo code in the workspace (a console version and a HTTP version)
//  3. update the user customized configurations, such as style.css
//  4. serve files of the user's workspace via HTTP
//
// Note: user [playground] is a reserved mock user
func addUser(userID, userName, userAvatar string) string {
	// addUserMutex.Lock()
	// defer addUserMutex.Unlock()
	userID = strings.ToLower(userID)
	user := conf.GetUser((userID))
	if user != nil {
		return userExists
	}
	///Users/boom/wide/workspaces/109
	workspace := filepath.Join(conf.Wide.Data, "workspaces", userID)
	newUser := conf.NewUser(userID, userName, userAvatar, workspace)
	//	conf.Users = append(conf.Users, newUser)
	if !newUser.Insert() {
		return userCreateError
	}

	if !conf.CreateWorkspaceDir(workspace) {
		initContractExample(workspace)
	}
	//conf.UpdateCustomizedConf(userID)

	log.Infof("Created a user [%s]", userID)

	return userCreated
}

func CreateUser(w http.ResponseWriter, r *http.Request) {
	result := gulu.Ret.NewResult()

	var params map[string]interface{}
	if err := json.NewDecoder(r.Body).Decode(&params); err != nil {
		log.Errorf("create user decode parameter failed. url:%s, err:%v", r.URL, err)
		result.Code = -1
		return
	}

	userID := params["uid"].(string)
	user := conf.GetUser(userID)
	if nil == user {
		msg := addUser(userID, userID, "")
		if userCreated != msg {
			result.Code = -1
			result.Msg = msg
			gulu.Ret.RetResult(w, r, result)
			return
		}
	}

	// create a HTTP session
	httpSession, _ := HTTPSession.Get(r, CookieName)
	httpSession.Values["uid"] = userID
	httpSession.Values["id"] = strconv.Itoa(rand.Int())
	httpSession.Options.MaxAge = conf.Wide.HTTPSessionMaxAge
	err := httpSession.Save(r, w)

	if err != nil {
		log.Errorf("[create user ] save session fail. uid:%s, err：%v", userID, err)
		result := gulu.Ret.NewResult()
		result.Code = -1
		result.Msg = err.Error()
		return
	}

	err = container.SC.ResourceManager.InitOrUpdateUserResourcesInfo(userID)
	if err != nil {
		log.Errorf("[create user ] init or update user resource info fail. uid:%s, err：%v", userID, err)
		result := gulu.Ret.NewResult()
		result.Code = -1
		result.Msg = err.Error()
		return
	}

	sid := WideSessions.GenId()
	wSession := WideSessions.Get(sid)
	if nil == wSession {
		wSession = WideSessions.newTestWideSession(httpSession, sid)
		log.Debugf("Created a wide session. sid [%s] user [%s]", sid, wSession.UserID)
	}
	_, err = httpSession.Store().Get(r, CookieName)

	if err != nil {
		log.Errorf("save session error.err: %s", err)
		result.Code = -1
		result.Msg = err.Error()
		gulu.Ret.RetResult(w, r, result)
		return
	}

	cookie := w.Header().Get("Set-Cookie")
	cookie = cookie[:strings.Index(cookie, ";")]

	responseData := make(map[string]string)
	responseData["uid"] = userID
	responseData["sid"] = sid
	responseData["cookie"] = cookie
	responseData["workSpace"] = conf.GetUserWorkspace(userID)
	result.Data = responseData
	gulu.Ret.RetResult(w, r, result)
	return
}

func initContractExample(workspace string) {
	dir := workspace + conf.PathSeparator + "src"
	err := conf.CpDockGoContractExToWS(dir)
	if err != nil {
		log.Error(err)
		return
	}
	return
}
